﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ProceduralCity
{
    class FPSCamera
    {
        readonly GraphicsDevice device;

        float moveSpeed;
        public float MoveSpeed
        {
            get
            {
                return moveSpeed;
            }
            set
            {
                moveSpeed = value;
            }
        }

        Vector3 position = Vector3.Zero;
        public Vector3 Position
        {
            get
            {
                return position;
            }
            set
            {
                position = value;
            }
        }

        Vector3 yawPitchRoll = Vector3.Zero;
        public Vector3 YawPitchRoll
        {
            get
            {
                return yawPitchRoll;
            }
            private set
            {
                yawPitchRoll = value;
                yawPitchRoll.Y = MathHelper.Clamp(yawPitchRoll.Y, -MathHelper.PiOver2, MathHelper.PiOver2);
                if (yawPitchRoll.X >= MathHelper.TwoPi)
                    yawPitchRoll.X -= MathHelper.TwoPi;
                if (yawPitchRoll.X <= -MathHelper.TwoPi)
                    yawPitchRoll.X += MathHelper.TwoPi;
                if (yawPitchRoll.Z >= MathHelper.TwoPi)
                    yawPitchRoll.Z -= MathHelper.TwoPi;
                if (yawPitchRoll.Z <= -MathHelper.TwoPi)
                    yawPitchRoll.Z += MathHelper.TwoPi;
            }
        }

        float scale = 1;
        public float Scale
        {
            get
            {
                return scale;
            }
            set
            {
                scale = value;
            }
        }

        float defaultPerspective = MathHelper.PiOver4; // TODO: could add a perspective prop too

        float viewDistance = 10 * 1000;
        public float ViewDistance
        {
            get
            {
                return viewDistance;
            }
            set
            {
                viewDistance = value;
            }
        }

        public Matrix View
        {
            get
            {
                // TODO: make this do lazy evaluation?
                Vector3 target = Position + TargetDirection;
                Matrix view = Matrix.CreateLookAt(Position, target, Up);
                return view;
            }
        }

        public Matrix Projection
        {
            get
            {
                float perspective = MathHelper.Clamp(defaultPerspective / scale, 0, MathHelper.Pi - 0.1f);
                Matrix projection = Matrix.CreatePerspectiveFieldOfView(perspective, device.Viewport.AspectRatio, 5f, ViewDistance);
                return projection;
            }
        }

        public Vector3 TargetDirection
        {
            get
            {
                Vector3 targetDirection = Vector3.Transform(Vector3.Forward, Matrix.CreateFromYawPitchRoll(YawPitchRoll.X, YawPitchRoll.Y, YawPitchRoll.Z));
                return targetDirection;
            }
        }

        public Vector3 Up
        {
            get
            {
                Vector3 up = Vector3.Transform(Vector3.Up, Matrix.CreateFromYawPitchRoll(YawPitchRoll.X, YawPitchRoll.Y, YawPitchRoll.Z));
                return up;
            }
        }


        public FPSCamera(GraphicsDevice device, float moveSpeed)
        {
            this.device = device;
            this.moveSpeed = moveSpeed;
        }

        public void LookAt(Vector3 target)
        {
            Vector3 direction = target - Position;
            direction.Normalize();

            float yaw = (float)Math.Atan2(-direction.X, -direction.Z);
            float ratio = direction.Y; // / (Math.Sign(-direction.Z) * (float)Math.Sqrt(Vector3.Dot(direction, direction))); // division not needed b/c we're using a normalized direction
            float pitch = (float)Math.Asin(ratio);
            YawPitchRoll = new Vector3(yaw, pitch, 0);
        }

        public void Rotate(float yaw, float pitch, float roll)
        {
            Vector3 additionalRot = new Vector3(yaw, pitch, roll);
            YawPitchRoll += additionalRot;
        }

        public void MoveRelativeForward(float deltaSeconds)
        {
            Vector3 delta = TargetDirection * deltaSeconds * moveSpeed;
            Position += delta;
        }

        public void MoveRelativeRight(float deltaSeconds)
        {
            Vector3 direction = Vector3.Transform(Vector3.Right, Matrix.CreateFromYawPitchRoll(YawPitchRoll.X, YawPitchRoll.Y, YawPitchRoll.Z));
            Vector3 delta = direction * deltaSeconds * moveSpeed;
            Position += delta;
        }

        public void MoveForwardInPlane(float deltaSeconds)
        {
            Vector3 direction = Vector3.Transform(Vector3.Forward, Matrix.CreateRotationY(YawPitchRoll.X));
            Vector3 delta = direction * deltaSeconds * moveSpeed;
            Position += delta;
        }

        public void MoveRealtiveUp(float deltaSeconds)
        {
            Vector3 delta = Up * deltaSeconds * moveSpeed;
            Position += delta;
        }

        public void MoveUp(float deltaSeconds)
        {
            Vector3 delta = Vector3.Up * deltaSeconds * moveSpeed;
            Position += delta;
        }
    }
}
